﻿using System;
using System.Collections.Generic;
using System.Linq;
using NDBGen.Descriptors.Columns.DataTypes;
using NDBGen.Descriptors.Columns.Tokens;
using NDBGen.Plugins.DataAccess;

namespace NDBGen.Plugins.Descriptors.Columns.DataTypes
{
    [ColumnDataTypeCodeSnippetGenerator(typeof(StandardDataTypeDescriptor))]
    public sealed class DotNetStandardDataTypeCodeGenerator : DotNetColumnDataTypeCodeGeneratorBase<StandardDataTypeDescriptor>
    {
        private const String TOSTRING_FORMAT = "{0}|{1}";
        private const String TOSTRING_LENGTH_FORMAT = "{0}|{1},{2}";

        private const String DBTYPE_FORMAT = "{0}";
        private const String DBTYPE_LENGTH_FORMAT = "{0}({1})";
        private const String DBTYPE_SCALE_FORMAT = "{0}({1},{2})";
        private const String CSHARP_PROPERTY_BODY_FORMAT = "public {0}{2} {1} {{ get; set; }}";

        protected override void Init(IPluginData pluginData, IColumnDataTypeDescriptor descriptor)
        {
            base.Init(pluginData, descriptor);

            String dbTypeFormat;
            if (this.Descriptor.Column.Scale.HasValue)
            {
                dbTypeFormat = DBTYPE_SCALE_FORMAT;
            }
            else if (this.Descriptor.Column.Length.HasValue || this.Descriptor.Column.DataType.IsMaxLengthType())
            {
                dbTypeFormat = DBTYPE_LENGTH_FORMAT;
            }
            else
            {
                dbTypeFormat = DBTYPE_FORMAT;
            }

            this.DbTypeDeclaration = String.Format(dbTypeFormat,
                this.Descriptor.Column.DataType.ToString().ToLower().Replace("max", String.Empty),
                (Object)this.Descriptor.Column.Length ?? (this.Descriptor.Column.DataType.IsMaxLengthType() ? "max" : String.Empty),
                (Object)this.Descriptor.Column.Scale ?? String.Empty);
        }

        public String DbTypeDeclaration { get; private set; }

        protected override Object[] GenerateColumnAttributeProperties()
        {
            var nullableDescriptor = this.Column.Descriptors.OfType<NullableTokenDescriptor>().Single();
            var decs = new List<Object>(new Object[]
            {
                ColumnMetadataHelper.ColumnAttribute.DbType.Format.SetArgs(this.DbTypeDeclaration, nullableDescriptor.IsActive ? String.Empty : " not")
            });

            if (this.Descriptor.Column.DataType.IsBigType())
            {
                decs.Add(ColumnMetadataHelper.ColumnAttribute.UpdateCheck.Never);
            }
            return decs.ToArray();
        }

        protected override Object[] GenerateCustomAttributes()
        {
            var decs = new List<Object>();
            if (this.Descriptor.Column.DataType.IsStringType() && this.Descriptor.Column.Length.HasValue)
            {
                decs.Add(ColumnMetadataHelper.CustomAttributes.StringLengthFormat.SetArgs(this.Descriptor.Column.Length));
            }
            return decs.ToArray();
        }

        protected override void WriteProperty(IIndentedTextWriter writer)
        {
            String typeName = DataTypeMapper.Map(this.Descriptor.Column.DataType).Name;
            var substitute = (SubstitutedDataTypeTokenDescriptor)this.Column.Descriptors.SingleOrDefault(m => m.GetType() == typeof(SubstitutedDataTypeTokenDescriptor));
            var enumValue = (EnumValueTokenDescriptor)this.Column.Descriptors.SingleOrDefault(m => m.GetType() == typeof(EnumValueTokenDescriptor));

            if (substitute != null) typeName = substitute.TokenValue;
            else if (enumValue != null)
            {
                typeName = this.Table.TypeName.ToPascalCase() + (this.Table.CanBeRepresentedByEnum ? String.Empty : "ID");
            }

            var property = String.Format(CSHARP_PROPERTY_BODY_FORMAT,
                typeName,
                substitute == null ? this.Descriptor.Column.Name.ToPascalCase() : (this.Descriptor.Column.Name.EndsWith("_id") ? this.Descriptor.Column.Name.Substring(0, this.Descriptor.Column.Name.Length - 3) : this.Descriptor.Column.Name).ToPascalCase(),
                this.Descriptor.RequiresNullableModifier(this.Column.Descriptors) ? ("?") : String.Empty
                );

            writer.WriteLine(property);
        }
    }
}